/*
 * @Description: 布局计算处理
 * @version:
 * @Author: E-CHOOSEUS R&D
 * @LastEditors: E-CHOOSEUS R&D
 */

import { cloneDeep } from 'lodash-unified'

import { mmToPx } from '@/utils/print'

// A4纸大小
export const pageA4 = {
  // "210mm": 793.688,
  '297mm': 1485,
  '210mm': 1050
}

const pageBaseMargin = {
  top: 20,
  right: 30,
  bottom: 20,
  left: 30
}
// 纸张基础设置
export const pageBaseInfo = {
  width: pageA4['297mm'],
  height: pageA4['210mm'],
  margin: {
    ...pageBaseMargin
  },
  available: {
    w: pageA4['297mm'] - pageBaseMargin.left - pageBaseMargin.right,
    h: pageA4['210mm'] - pageBaseMargin.top - pageBaseMargin.bottom
  }
}

/**
 * @description 获取每行渲染需要的高度
 * @param {*} rowList
 */
export function getRowRenderHeight(rowList) {
  const domTable = document.querySelector('#preRenderDoc #preRenderTable-body')
  if (domTable) {
    const domRows = domTable.querySelectorAll('.icr-preRenderRow')

    for (let i = 0, len = domRows.length; i < len; ++i) {
      const domRow = domRows[i]
      rowList[i] && (rowList[i].height = domRow.offsetHeight || 0)
    }
  }
}

/**
 * @description 表头横向分页优化算法
 * @param {*} tree 旧树
 * @param {*} nextNode 下一个待分配叶子节点
 * @param {*} fullMap 全量节点 map
 * @returns {Object}
 *    {isNew} nextNode 与 tree 的关系
 *    {pushWidth} isNew为true时, 推入pushWidth宽度
 *    {nextWidth} nextWidth isNew为true时, 新树的宽度
 *    {tree} 每次生成的树, 可能为新树, 也可能为旧树, 但总是可信的, 可重复传传递给方法首参 tree
 */
function optimizeColumnsWidthCost(tree, nextNode, fullMap) {
  function isNew(tree, insetNode, fullMap) {
    if (!tree) {
      return true
    }
    if (!insetNode) {
      return true
    }
    if (!insetNode._pUuid) {
      return true
    }
    /**
     * @description BFS查找节点, 找到返回true, 否则为false
     * @param {*} sTree 查找树
     * @param {*} node 查找节点
     * @returns
     */
    const bfs = (sTree, node) => {
      if (!sTree || !node) {
        return false
      }
      if (sTree._uuid === node._uuid) {
        return true
      }
      const childList = sTree._childList || []
      for (const childNode of childList) {
        const result = bfs(childNode, node)
        if (result) {
          return true
        }
      }
      return false
    }

    // * 查看当前节点是否在
    if (bfs(tree, insetNode)) {
      return false
    }

    let upUuid = insetNode._pUuid
    while (upUuid) {
      // ! 如果 upNode 为空值, 不应该出现这种情况
      const upNode = fullMap.get(upUuid)
      const result = bfs(tree, upNode)
      if (result) {
        return false
      }

      upUuid = upNode._pUuid // 向上追溯
    }
    return true
  }

  // * 计算树的宽度
  const getTreeCostWidth = tree => {
    // console.log(tree)
    if (!tree) {
      return 0
    }
    const widthList = []
    widthList.push(tree._width)

    const childList = tree._childList || []
    let childWidth = 0
    for (const child of childList) {
      childWidth += getTreeCostWidth(child)
    }
    widthList.push(childWidth)

    return Math.max(...widthList)
  }

  /**
   * @description 构造原始树
   * @param {*} node
   * @param {*} fullMap
   * @returns
   */
  const buildTree = (node, fullMap) => {
    let tree = null

    let searchUuid = node._uuid
    while (searchUuid) {
      const searchNode = cloneDeep(fullMap.get(searchUuid))
      if (searchUuid) {
        Object.assign(searchNode, { childNode: [], jgtChild: [] })
        searchNode._childList = []

        if (!tree) {
          tree = searchNode
        } else {
          searchNode._childList.push(tree)
          tree = searchNode
        }
      }
      // ? 防止死循环
      searchUuid = searchNode ? searchNode._pUuid : null
    }
    return tree
  }

  const nodeInsertTree = (tree, insertNode, fullMap) => {
    if (tree._uuid === insertNode._pUuid) {
      !Array.isArray(tree._childList) && (tree._childList = [])
      tree._childList.push(
        Object.assign(cloneDeep(insertNode), { childNode: [], jgtChild: [] })
      )
      return tree
    }
    const childList = tree._childList || []
    // 是否是新子节点的下层节点
    let isNewChildSubNode = true
    for (const child of childList) {
      // * 此处非常容易理解错误, 这里 isNew() 方法的期望返回为 false, 表示新节点属于某颗子树下 判断条件前应该加个 !
      if (!isNew(child, insertNode, fullMap)) {
        isNewChildSubNode = false
        nodeInsertTree(child, insertNode, fullMap)
      }
    }
    // * 此处 isNewChildSubNode 应为 true, 表示该节点目前不在当前子节点列表下
    if (isNewChildSubNode) {
      const childTree = buildTree(insertNode, fullMap)

      /**
       * @description 寻找目标节点的子节点列表
       * @param {*} tree
       * @param {*} targetNode
       * @returns
       */
      const findNodeChildList = (tree, targetNode) => {
        if (tree._uuid === targetNode._uuid) {
          return tree._childList || []
        }
        const childList = tree._childList || []
        for (const child of childList) {
          const result = findNodeChildList(child, targetNode)
          if (result.length) {
            return result
          }
        }
        return []
      }

      const newChildList = findNodeChildList(childTree, tree)

      // console.log("childTree, newChildList: ", childTree, newChildList, tree);

      !Array.isArray(tree._childList) && (tree._childList = [])
      for (const child of newChildList) {
        tree._childList.push(child)
      }
    }
    return tree
  }

  if (isNew(tree, nextNode, fullMap)) {
    const treeMaxWidth = getTreeCostWidth(tree)
    const newTree = buildTree(nextNode, fullMap)
    const newTreeMaxWidth = getTreeCostWidth(newTree)
    return {
      isNew: true,
      pushWidth: treeMaxWidth,
      nextWidth: newTreeMaxWidth,
      tree: newTree
    }
  }

  const newTree = nodeInsertTree(tree, nextNode, fullMap)
  const newTreeMaxWidth = getTreeCostWidth(newTree)

  return {
    isNew: false,
    pushWidth: newTreeMaxWidth,
    nextWidth: 0,
    tree: newTree
  }
}

/**
 * TODO 算法有待优化
 * @description 处理表头列分页
 * @param {*} leafMap
 * @param {*} tree
 * @param {*} pageInfo
 * @returns
 */
export function getPagesColumn(
  leafMap,
  tree,
  pageInfo = pageBaseInfo,
  fullMap
) {
  const pagingResult = {
    pagesInfo: [],
    pagesColumn: []
  }
  let sumWidth = 0 // * 总渲染宽度
  const leafColumns = [] // * 全部的叶子节点
  const repeatColumns = []
  let repeatWidth = 0 // 重复渲染列宽度
  const pagesX = 1 // * 横向分页数
  for (const value of leafMap.values()) {
    sumWidth += value._width
    leafColumns.push(value)
    if (value._repeat) {
      repeatColumns.push(value)
      repeatWidth += value._width
    }
  }

  const pageList = []

  // * 横向不需要分页
  // if (sumWidth <= pageInfo.available.w) {
  //   pageList.push(cloneDeep(leafColumns));

  //   const pageInfo = {
  //     pageXWidth: sumWidth, // 页面宽度
  //   };
  //   pagingResult.pagesInfo.push(pageInfo);
  //   pagingResult.pagesColumn = pageList;
  //   console.log("pagingResult: ", pagingResult);
  //   return pagingResult;
  // }

  // // ! 该计算方式出现横向分页缺失几率较高
  // // * 当纸张横向可渲染空间 小于 渲染总长度
  // while (pagesX * pageInfo.available.w < sumWidth + repeatWidth * (pagesX - 1)) {
  //   pagesX = pagesX + 1; // 横向分页数 +1
  // }

  const pagesInfo = [] // * 分页信息
  const pages = [] // ? 横向分页数组 （原始数组, 需要二次处理）

  let allotCount = 0 // * 已分配的列数
  let p = 0
  while (allotCount < leafColumns.length) {
    const pageColumns = [] // 当前页
    let contentWidth = 0 // 当前页占据的宽度

    // * 从第二页开始, 将重复项推入, 占据的宽度也纳入计算
    if (p > 0) {
      pageColumns.push(...cloneDeep(repeatColumns))
      contentWidth = repeatWidth
    }

    let compareContentWidth = contentWidth // 修正的内容比较宽度
    let compareTree = null
    let prevResult = null
    for (let i = allotCount; i < leafColumns.length; ++i) {
      const iColumn = leafColumns[i]
      contentWidth += iColumn._width

      const nextNodeWidth = iColumn._dTreeMaxW || iColumn._width // 根据叶子节点纵深树的最大宽度来决定是否推入当前页面

      const result = optimizeColumnsWidthCost(compareTree, iColumn, fullMap)
      // console.log(result, iColumn)
      compareTree = result.tree

      /* console.log(
          "p, compareContentWidth, result, prevResult, pageBaseInfo.available.w: ",
          p,
          compareContentWidth,
          result,
          prevResult,
          pageBaseInfo.available.w
        ); */
      // TODO 代码较乱, 需要整理
      // * 分页算法已优化
      if (
        compareContentWidth + result.pushWidth + result.nextWidth <=
        pageInfo.available.w
      ) {
        pageColumns.push(cloneDeep(iColumn))
        if (result.isNew) {
          compareContentWidth += result.pushWidth
        }
        ++allotCount
        if (allotCount === leafColumns.length) {
          // console.log("i, compareContentWidth, result: ", i, compareContentWidth, result);
          pagesInfo.push({ pageXWidth: compareContentWidth + result.nextWidth })
        }
      } else {
        // * 处理开启下一分页前当前分页宽度计算
        let curPageWidth = compareContentWidth
        if (result.isNew) {
          curPageWidth += result.pushWidth
        } else {
          if (prevResult) {
            !prevResult.isNew &&
              !result.isNew &&
              (curPageWidth += prevResult.pushWidth) // ? 皆为新树时
            curPageWidth += prevResult.nextWidth
          }
        }
        pagesInfo.push({ pageXWidth: curPageWidth })
        compareTree = null
        ++p // 分页 +1
        break // 跳出当前循环
      }
      prevResult = result
    }

    pages.push(pageColumns)
  }

  pagingResult.pagesInfo = pagesInfo
  pagingResult.pagesColumn = pages

  // console.log("pagesInfo: ", pagesInfo);
  // console.log("pagesColumn：", pages);

  return pagingResult
}

/**
 * @tutorial 理解难度: ★★★★
 * @description 将叶子节点列表转换为树
 * @param {*} fullMap 全量节点map
 * @param {*} pagesColumn 分页的列
 * @returns
 */
export function parseColumnListToTree(fullMap, pagesColumn) {
  const list = []

  /**
   * TODO 该方法未测试
   * @description 判断对比节点是否属于查找树 【即节点不在树中, 返回 true, 在树中, 则返回 false】
   * @param {*} tree 查找树
   * @param {*} insetNode 对比节点
   * @returns {boolean}  是/否
   */
  function isNewTree(tree, insetNode) {
    // console.log("tree, insetNode: ", tree, insetNode);
    if (!tree) {
      return true
    }
    if (!insetNode) {
      return true
    }
    if (!insetNode._pUuid) {
      return true
    }
    // bfs 搜索
    const bfs = (sTree, node) => {
      if (!sTree || !node) {
        // console.log("sTree, node: ", sTree, node);
      }
      if (sTree._uuid === node._uuid) {
        return true
      }
      const childList = sTree._childList || []
      for (const childNode of childList) {
        const result = bfs(childNode, node)
        if (result) {
          return true
        }
      }
      return false
    }

    // * 查看当前节点是否在
    if (bfs(tree, insetNode)) {
      // console.log("bfs(tree, insetNode)");
      return false
    }

    let upUuid = insetNode._pUuid
    while (upUuid) {
      // ! 如果 upNode 为空值, 不应该出现这种情况
      const upNode = fullMap.get(upUuid)
      const result = bfs(tree, upNode)
      if (result) {
        // console.log("const result = bfs(tree, upNode);");
        return false
      }

      upUuid = upNode._pUuid // 向上追溯

      // if (upNode) {
      // } else {
      //   // eslint-disable-next-line no-console
      //   console.error("isNewTree() upNode is undefined, upNode: ", upNode);
      // }
    }
    // console.log("return false;");
    return true
  }

  /**
   * @description 构造节点树
   * @param {*} newNode
   * @returns
   */
  function buildTree(newNode) {
    let tree = null
    let upUuid = newNode._pUuid
    // 存在父级
    if (upUuid) {
      while (upUuid) {
        const pNode = fullMap.get(upUuid)

        const root = cloneDeep(pNode)
        root._childList = []
        if (!tree) {
          root._childList.push(cloneDeep(newNode))
          tree = root
        } else {
          root._childList.push(tree)
          tree = root
        }
        upUuid = pNode._pUuid // 向上追溯
      }

      return tree
    }
    // 不存在父级
    const root = cloneDeep(newNode)
    root._childList = []
    tree = root
    return tree
  }

  function buildSubTreeById(newNode, targetId) {
    let tree = null
    let upUuid = newNode._pUuid
    // 存在父级
    if (upUuid) {
      while (upUuid) {
        const pNode = fullMap.get(upUuid)

        const root = cloneDeep(pNode)
        root._childList = []
        if (!tree) {
          root._childList.push(cloneDeep(newNode))
          tree = root
        } else {
          root._childList.push(tree)
          tree = root
        }
        // * 这一句是关键 找到指定的父级id
        if (targetId === upUuid) {
          return tree._childList
        }
        upUuid = pNode._pUuid // 向上追溯
      }

      return tree
    }
    // ! 不应该走到这里, 所以此处报错
    // eslint-disable-next-line no-console
    console.error('buildSubTreeById is error')
    // 不存在父级
    // const root = cloneDeep(newNode);
    // root._childList = [];
    // tree = root;
    return tree
  }

  /**
   * @description 树中插入新节点（子树）
   * @param {*} tree
   * @param {*} newNode
   * @returns
   */
  function getNodeTree(tree, newNode) {
    // 从树根开始查找
    if (tree._uuid === newNode._pUuid) {
      tree._childList.push(cloneDeep(newNode))
      return tree
    }
    const childList = tree._childList || []

    let isNew = true // 是否子级新树
    for (const childNode of childList) {
      if (!isNewTree(childNode, newNode)) {
        isNew = false
        getNodeTree(childNode, newNode)

        return tree
      }
    }
    if (isNew) {
      const subTree = buildSubTreeById(newNode, tree._uuid)
      // console.log("subTree: ", subTree);
      // childList.push(subTree);
      tree._childList = childList.concat(subTree)
    }

    return tree
  }

  for (let p = 0, pLen = pagesColumn.length; p < pLen; ++p) {
    const columns = pagesColumn[p]

    const pageTreeColumns = []
    let tree = null
    let i = 0
    while (i < columns.length) {
      // todo: 第一步 如果不归属于当前 tree,
      const iNode = columns[i]
      ++i // ? 写在取值之后, 避免遗漏

      // ? 完成新节点与现有树的关系对比
      const differentTree = isNewTree(tree, iNode)
      // console.log("differentTree: ", differentTree);
      // ! 不是一棵树
      if (differentTree) {
        // todo: 第二步 推入列表，并清空
        // tree = getNodeTree(null, iNode); // * 构造一颗新树, 此时 首参应该为 null
        tree = buildTree(iNode) // 抽离的构造树
        pageTreeColumns.push(tree)
        // ++i;
        continue
      }

      // todo: 继续构造树
      getNodeTree(tree, iNode) // ? 如果 iNode 没有 pId, 走不到这里来
    }

    // if (tree) {
    //   pageTreeColumns.push(tree);
    //   tree = null;
    // }

    list.push(pageTreeColumns)
  }

  return list
}

/**
 * @description 获取纵向分页排版处理
 * @param {*} tableList
 * @param {*} pageInfo
 * @param {*} otherLayoutInfo
 * @returns
 */
export function getPagesYDataPagingInfo(
  tableList,
  pageInfo = pageBaseInfo,
  otherLayoutInfo = {
    tableHeadHeight: 0,
    docHeaderHeight: 0,
    docFooterHeight: 0,
    medicalHistoryHeight: 0
  }
) {
  // console.log("tableList, pageInfo, otherLayoutInfo: ", tableList, pageInfo, otherLayoutInfo);
  // 纵向排版信息列表
  const pagesYSettings = []

  const contentAvaY = pageInfo.available.h // 纸张可渲染高度

  // * 已知的内容高度
  const accessContentHeight =
    otherLayoutInfo.tableHeadHeight +
    otherLayoutInfo.docHeaderHeight +
    otherLayoutInfo.docFooterHeight
  // * 表格高度（每次计算时, 需要将表头高度算上），首次需要加上简要病史高度
  let tableHeight =
    0 + accessContentHeight + otherLayoutInfo.medicalHistoryHeight
  // 数据行信息
  const settingItem = {
    startAt: 0,
    endAt: 0,
    len: 0
  }

  let startIdx = 0 // 记录每一次垂直分页数据行开始索引
  for (let i = 0, len = tableList.length; i < len; ++i) {
    const row = tableList[i]
    // console.log("contentAvaY, tableHeight, row.height: ", contentAvaY, tableHeight, row.height);
    tableHeight += row.height
    // 表格高度超过可用空间
    if (contentAvaY < tableHeight) {
      const cpItem = {
        ...settingItem,
        startAt: startIdx,
        endAt: i - 1,
        len: i - startIdx
      }

      pagesYSettings.push(cpItem)

      // * 重置索引信息
      startIdx = i
      tableHeight = 0 + accessContentHeight + row.height
    }
  }
  // ! 高度可能不需要分页
  // if (startIdx) {
  settingItem.startAt = startIdx
  settingItem.endAt = tableList.length - 1
  settingItem.len = tableList.length - startIdx

  pagesYSettings.push(settingItem)
  startIdx = 0
  // }

  return pagesYSettings
}

/**
 * @description 修正数据行与表头的宽度偏差
 * @param {*} parsedPagesColumn 已追溯出树形结构的横线表头分页列表
 * @returns
 */
export function fixedDataCellWidth(parsedPagesColumn) {
  const pagesXCellList = []
  for (let p = 0, len = parsedPagesColumn.length; p < len; ++p) {
    const pageColumns = parsedPagesColumn[p]

    const fixedWidthList = []

    for (let i = 0, iLen = pageColumns.length; i < iLen; ++i) {
      const l1Node = pageColumns[i]

      // * 如果
      if (l1Node.isLeaf) {
        const l1fixWidth = {
          [l1Node._uuid]: l1Node.width
        }
        fixedWidthList.push(l1fixWidth)
      }

      const l1childList = l1Node.childList || []
      for (let j = 0, jLen = l1childList.length; j < jLen; ++j) {
        const l2Node = l1childList[j]

        if (l2Node.isLeaf) {
          const l2fixWidth = {
            [l2Node._uuid]: l2Node.width
          }
          fixedWidthList.push(l2fixWidth)

          // TODO 该条件未明确
          // * 修正二级的最后一列宽度
          j + 1 === jLen && (l2fixWidth[l2Node._uuid] = l2Node.width + 1)
        }

        const l2ChildList = l2Node.childList || []
        for (let k = 0, kLen = l2ChildList.length; k < kLen; ++k) {
          const l3Node = l2ChildList[k]

          if (l3Node.isLeaf) {
            const l3fixWidth = {
              [l3Node._uuid]: l3Node.width
            }
            fixedWidthList.push(l3fixWidth)

            // * 修正超过第三级的最后一列宽度
            k + 1 === kLen && (l3fixWidth[l3Node._uuid] = l3Node.width + 1)
          }
        }
      }
    }

    pagesXCellList.push(fixedWidthList)
  }

  return pagesXCellList
}

/**
 * @description 顶部额外内容列表分页
 * @param {*} docData
 * @param {*} pageInfo
 * @param {*}  otherLayoutInfo
 * @returns
 */
export function getDocTopExtendPages(
  docData,
  pageInfo = pageBaseInfo,
  otherLayoutInfo = {
    tableHeadHeight: 0,
    docHeaderHeight: 0,
    docFooterHeight: 0
  }
) {
  const docTopMaxList =
    docData.scoreRecord.length > docData.respList.length
      ? docData.scoreRecord
      : docData.respList

  const contentAvaY = pageInfo.available.h
  const accessContentHeight =
    // otherLayoutInfo.tableHeadHeight +
    otherLayoutInfo.docHeaderHeight + otherLayoutInfo.docFooterHeight + 40
  const remainingHeight = contentAvaY - accessContentHeight

  // table的每行高度目前先写死40px
  let currentHeight = 0
  const pages = []
  let list = []
  docTopMaxList.forEach((v, index) => {
    if (currentHeight + 40 >= remainingHeight) {
      pages.push(list)
      list = [index]
      currentHeight = 40
    } else {
      currentHeight += 40
      list.push(index)
    }
  })
  if (list.length > 0) {
    pages.push(list)
  }

  return pages
}
